package com.wilutions.itol;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.controlsfx.control.CheckComboBox;
import com.sun.javafx.scene.control.skin.TextAreaSkin;
import com.wilutions.fx.acpl.AutoCompletionComboBox;
import com.wilutions.fx.acpl.AutoCompletions;
import com.wilutions.fx.acpl.ExtractImage;
import com.wilutions.fx.util.DateTimePicker;
import com.wilutions.fx.util.TextFieldWithSuggestions;
import com.wilutions.itol.db.ISODate;
import com.wilutions.itol.db.IdName;
import com.wilutions.itol.db.Issue;
import com.wilutions.itol.db.IssuePropertyEditor;
import com.wilutions.itol.db.IssueService;
import com.wilutions.itol.db.Property;
import com.wilutions.itol.db.PropertyClass;
import com.wilutions.itol.db.Suggest;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
public class PropertyGridView {
private final IssueTaskPane issueTaskPane;
private final GridPane propGrid;
private final TabPane tabpIssue;
private final Tab tabProperties;
private final String tabPropertiesTitle;
public final static Insets DEFAULT_PADDING = new Insets(8);
private final ResourceBundle resb = Globals.getResourceBundle();
private Node firstControl;
private Logger log = Logger.getLogger("PropertyGridView");
private final List<PropertyNode> propNodes = new ArrayList<>();
public PropertyGridView(IssueTaskPane issueTaskPane, TabPane tabpIssue, Tab tabProperties, GridPane propGrid) throws IOException {
this.issueTaskPane = issueTaskPane;
this.propGrid = propGrid;
this.tabpIssue = tabpIssue;
this.tabProperties = tabProperties;
this.tabPropertiesTitle = tabProperties.getText();
ColumnConstraints constr0 = propGrid.getColumnConstraints().get(0);
constr0.setPercentWidth(30);
}
/**
* Replace current content of properties tab by given node.
* This function is called by a property view, if button "..." is pressed.
* @param replaceBy Content is replaced by this node
* @param title This string is inserted into the tab title.
* @param padding Padding for replaceBy node.
*/
public void pushPropertyGrid(Node replaceBy, String title, Insets padding) {
// Prepare the tab content for displaying the Node replaceBy
Pane box = (Pane)tabProperties.getContent();
box.setPadding(padding);
HBox.setHgrow(replaceBy, Priority.ALWAYS);
// Add replaceBy to tab content
box.getChildren().add(replaceBy);
// Hide previous tab content
Region scrollPane = (Region)box.getChildren().get(0);
scrollPane.setVisible(false);
scrollPane.setManaged(false);
// Append tile for replaceBy Node
tabProperties.setText(title + " (" + tabPropertiesTitle + ")");
// Make tab header orange
tabpIssue.getStyleClass().add("sub-prop-tab");
// Disable all other tabs
tabpIssue.getTabs().stream().filter((tab) -> tab != tabProperties).forEach((tab) -> tab.setDisable(true));
issueTaskPane.setNextTabDisable(true);
}
/**
* Restore view of property tab.
*/
public void popPropertyGrid() {
Pane tabVBox = (Pane)tabProperties.getContent();
if (tabVBox.getChildren().size() > 1) {
// Restore padding
tabVBox.setPadding(DEFAULT_PADDING);
// Show original properties view
Node scrollPane = tabVBox.getChildren().get(0);
scrollPane.setVisible(true);
scrollPane.setManaged(true);
// Remove replacement
tabVBox.getChildren().remove(1);
// Restore properties tab title
tabProperties.setText(tabPropertiesTitle);
// Show tab title in default color
tabpIssue.getStyleClass().remove("sub-prop-tab");
// Enable all other tabs
tabpIssue.getTabs().stream().filter((tab) -> tab != tabProperties).forEach((tab) -> tab.setDisable(false));
issueTaskPane.setNextTabDisable(false);
}
}
public void initProperties(Issue issue) throws Exception {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "PropertyGridView(");
popPropertyGrid();
// Cleanup current view.
propGrid.getChildren().clear();
propGrid.getRowConstraints().clear();
propNodes.clear();
int rowIndex = 0;
// Add propert for create date.
if (!issue.isNew()) {
addPropertyForCreateDate(issue, rowIndex++);
}
IssueService srv = Globals.getIssueService();
List<String> propOrder = srv.getPropertyDisplayOrder(issue);
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "propOrder=" + propOrder);
for (String propertyId : propOrder) {
if (isPropertyForGrid(propertyId)) {
PropertyClass propClass = srv.getPropertyClass(propertyId, issue);
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "propClass=" + propClass);
if (propClass != null) {
addProperty(issue, propClass, rowIndex++);
}
}
}
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")PropertyGridView");
}
private void addPropertyForCreateDate(Issue issue, int rowIndex) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "prop for create date=" + issue.getCreateDate());
LocalDateTime ldate = issue.getCreateDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
String text = ldate.format(DateTimeFormatter.ofPattern(DateTimePicker.DefaultFormat));
TextField ctrl = new TextField(text);
ctrl.setEditable(false);
// ctrl.setMouseTransparent(true); cannot be activated by mouse clicks
ctrl.setFocusTraversable(false);
Label label = new Label(resb.getString("IssueProperty.CreateDate"));
propGrid.add(label, 0, rowIndex);
propGrid.add(ctrl, 1, rowIndex);
}
public void updateData(boolean save) {
for (PropertyNode propNode : propNodes) {
propNode.updateData(save);
}
}
private boolean isPropertyForGrid(String propertyId) {
boolean ret = true;
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "isPropertyForGrid=" + ret);
return ret;
}
private void addProperty(Issue issue, PropertyClass pclass, int rowIndex) throws Exception {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "addProperty(" + pclass);
Label label = new Label();
String labelText = pclass.getName();
if (pclass.isRequired()) {
labelText += " *";
//label.setStyle("-fx-font-weight: bold");
}
label.setText(labelText);
propGrid.add(label, 0, rowIndex);
GridPane.setValignment(label, VPos.TOP);
PropertyNode propNode = makeIssuePropertyEditor(issue, pclass);
if (propNode == null) {
List<IdName> selectList = pclass.getSelectList();
Suggest<IdName> suggest = pclass.getAutoCompletionSuggest();
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "selectList=" + selectList + ", suggest=" + suggest);
Property prop = issue.getCurrentUpdate().getProperty(pclass.getId());
if (prop != null && prop.getValue() == null) {
Object defaultValue = pclass.getDefaultValue();
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "defaultValue=" + defaultValue);
prop.setValue(defaultValue);
}
switch (pclass.getType()) {
case PropertyClass.TYPE_ISO_DATE:
propNode = makeDatePickerForProperty(issue, pclass);
break;
case PropertyClass.TYPE_ISO_DATE_TIME:
propNode = makeDateTimePickerForProperty(issue, pclass);
break;
case PropertyClass.TYPE_BOOL:
propNode = makeCheckBoxForProperty(issue, pclass);
break;
case PropertyClass.TYPE_TEXT:
propNode = makeTextAreaForProperty(issue, pclass);
break;
case PropertyClass.TYPE_FLOAT:
propNode = makeNumericFieldForProperty(issue, pclass);
break;
// case PropertyClass.TYPE_INTEGER:
// ctrl = makeIntFieldForProperty(prop);
// break;
case PropertyClass.TYPE_ID_NAME:
{
if (selectList != null) {
if (pclass.isArray()) {
propNode = makeChoiceBoxForPropertyArray(issue, pclass);
}
else {
propNode = makeChoiceBoxForProperty(issue, pclass);
}
}
else if (suggest != null) {
if (pclass.isArray()) {
propNode = makeAutoCompletionNodeArray(issue, pclass);
}
else {
propNode = makeAutoCompletionNode(issue, pclass);
}
}
else {
// IdName field without selection list - unsupported
log.warning("Unsupported property class " + pclass + ".");
}
}
break;
default:
{
propNode = (pclass.getAutoCompletionSuggest() != null) ?
makeTextFieldWithSuggestions(issue, pclass):
makeTextFieldForProperty(issue, pclass);
}
}
}
if (propNode != null) {
Node ctrl = propNode.getNode();
// Set node read-only
ctrl.setDisable(pclass.isReadOnly());
// Node shall occupy the entire width.
if (ctrl instanceof Region) {
Region region = (Region)ctrl;
region.setMaxWidth(Double.MAX_VALUE);
region.setPrefWidth(Double.MAX_VALUE);
}
propGrid.add(ctrl, 1, rowIndex);
if (rowIndex == 0) {
firstControl = ctrl;
}
propNodes.add(propNode);
}
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")addProperty");
}
private class AutoCompletionNodeArray_FirstButton extends Button {
AutoCompletionNodeArray_FirstButton(VBox vbox, Property prop, Issue issue, PropertyClass pclass) {
super("+");
this.setMaxWidth(25);
this.setOnAction(new AutoCompletionNodeArray_onAdd(vbox, prop, issue, pclass) {
@Override
public void handle(ActionEvent event) {
vbox.getChildren().remove(0);
super.handle(event);
}
});
}
}
private class AutoCompletionNodeArray_onAdd implements EventHandler<ActionEvent> {
VBox vbox;
Property prop;
Issue issue;
PropertyClass pclass;
AutoCompletionNodeArray_onAdd(VBox vbox, Property prop, Issue issue, PropertyClass pclass) {
this.vbox = vbox;
this.prop = prop;
this.issue = issue;
this.pclass = pclass;
}
@Override
public void handle(ActionEvent event) {
final HBox thisHBox = new HBox();
thisHBox.setSpacing(4);
Button bnDelete = new Button("x");
bnDelete.setMaxWidth(25);
bnDelete.setPrefWidth(25);
bnDelete.setOnAction((_ignored) -> {
List<Node> vchildren = vbox.getChildren();
vchildren.remove(thisHBox);
if (vchildren.isEmpty()) {
vchildren.clear();
Button firstButton = new AutoCompletionNodeArray_FirstButton(vbox, prop, issue, pclass);
vchildren.add(firstButton);
firstButton.requestFocus();
}
else {
HBox lastRow = (HBox)vchildren.get(vchildren.size()-1);
Button bnAdd_1 = (Button)lastRow.getChildren().get(1);
bnAdd_1.setDisable(false);
}
});
Node ed = makeAutoCompletionNode(issue, pclass).getNode();
if (ed instanceof Region) {
((Region)ed).setMaxWidth(Double.MAX_VALUE);
}
HBox.setHgrow(ed, Priority.ALWAYS);
Button bnAdd2 = new Button("+");
bnAdd2.setPrefWidth(25);
bnAdd2.setMaxWidth(25);
// bnAdd2.setStyle("-fx-background-color: white");
bnAdd2.setOnAction(new AutoCompletionNodeArray_onAdd(vbox, prop, issue, pclass));
List<Node> children = vbox.getChildren();
if (!children.isEmpty()) {
HBox hbox_1 = (HBox)children.get(children.size()-1);
Button bnAdd_1 = (Button)hbox_1.getChildren().get(1);
bnAdd_1.setDisable(true);
}
thisHBox.getChildren().addAll(ed, bnAdd2, bnDelete);
children.add(thisHBox);
ed.requestFocus();
}
};
private PropertyNode makeAutoCompletionNodeArray(Issue issue, PropertyClass pclass) {
VBox vbox = new VBox();
vbox.setSpacing(4);
Property prop = issue.getCurrentUpdate().getProperty(pclass.getId());
Button bnAdd = new AutoCompletionNodeArray_FirstButton(vbox, prop, issue, pclass);
vbox.getChildren().add(bnAdd);
PropertyNode propNode = new PropertyNode(issue, pclass, vbox) {
@Override
public void updateData(boolean save) {
if (save) {
}
else {
}
}
};
return propNode;
}
private static class CheckedIdName extends IdName {
private static final long serialVersionUID = -5331330727211218988L;
private final SimpleBooleanProperty checked = new SimpleBooleanProperty(false);
public CheckedIdName(IdName rhs, boolean checked) {
super(rhs);
this.checked.set(checked);
}
public SimpleBooleanProperty checkedProperty() {
return checked;
}
}
@SuppressWarnings({ "rawtypes", "unused" })
private Region makeMultiListBoxForProperty(Property prop, Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeMultiListBoxForProperty(");
List<IdName> selectList = pclass.getSelectList();
Object propValue = prop != null ? prop.getValue() : null;
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "value=" + propValue);
List<CheckedIdName> items = new ArrayList<CheckedIdName>(selectList.size());
for (IdName idn : selectList) {
boolean checked = false;
if (propValue != null) {
if (propValue instanceof List) {
switch(pclass.getType()) {
case PropertyClass.TYPE_STRING:
checked = ((List)propValue).contains(idn.getId());
break;
case PropertyClass.TYPE_ID_NAME:
checked = ((List)propValue).contains(idn);
break;
}
}
else if (propValue instanceof String) {
switch(pclass.getType()) {
case PropertyClass.TYPE_STRING:
checked = propValue.equals(idn.getId());
break;
case PropertyClass.TYPE_ID_NAME:
checked = propValue.equals(idn);
break;
}
}
}
items.add(new CheckedIdName(idn, checked));
}
ListView<CheckedIdName> lb = new ListView<CheckedIdName>();
Callback<CheckedIdName, ObservableValue<Boolean>> getProperty = new Callback<CheckedIdName, ObservableValue<Boolean>>() {
@Override
public BooleanProperty call(CheckedIdName idn) {
return idn.checkedProperty();
}
};
Callback<ListView<CheckedIdName>, ListCell<CheckedIdName>> forListView = CheckBoxListCell
.forListView(getProperty);
lb.setCellFactory(forListView);
lb.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
lb.setItems(FXCollections.observableArrayList(items));
lb.setMinHeight(25);
lb.setPrefHeight(25 * items.size());
lb.setMaxHeight(25 * 6);
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeMultiListBoxForProperty");
return lb;
}
private PropertyNode makeTextFieldForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeTextFieldForProperty(");
TextField ed = new TextField();
PropertyNode propNode = new PropertyNode(issue, pclass, ed) {
@Override
public void updateData(boolean save) {
if (save) {
String text = ed.getText();
Object value = null;
if (pclass.isArray()) {
List<String> list = Arrays.asList(text.split(" "));
value = list;
}
else {
value = text;
}
issue.setPropertyValue(pclass.getId(), value);
}
else {
Object value = issue.getPropertyValue(pclass.getId(), null);
String text = "";
if (value != null) {
if (pclass.isArray()) {
@SuppressWarnings("rawtypes")
List list = (List)value;
StringBuilder sbuf = new StringBuilder();
for (Object elm : list) {
if (sbuf.length() != 0) sbuf.append(" ");
sbuf.append(elm);
}
}
else {
text = value.toString();
}
}
ed.setText(text);
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeTextFieldForProperty");
return propNode;
}
private PropertyNode makeTextFieldWithSuggestions(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeTextFieldForProperty(");
TextField ed = new TextFieldWithSuggestions<IdName>(pclass.getAutoCompletionSuggest());
PropertyNode propNode = new PropertyNode(issue, pclass, ed) {
@SuppressWarnings("unchecked")
@Override
public void updateData(boolean save) {
if (save) {
String text = ed.getText();
if (pclass.isArray()) {
ArrayList<String> list = new ArrayList<String>();
StringTokenizer stok = new StringTokenizer(text, TextFieldWithSuggestions.DELIMS);
while (stok.hasMoreTokens()) {
String tok = stok.nextToken();
list.add(tok);
}
issue.setPropertyValue(pclass.getId(), list);
}
else {
issue.setPropertyValue(pclass.getId(), text);
}
}
else {
String value = "";
if (pclass.isArray()) {
// Handling for JIRA property type "Label"
StringBuilder sbuf = new StringBuilder();
List<String> values = (List<String>)issue.getPropertyValue(pclass.getId(), new ArrayList<String>());
for (int i = 0; i < values.size(); i++) {
String value_i = values.get(i);
if (i != 0) sbuf.append(" ");
sbuf.append(value_i);
}
value = sbuf.toString();
}
else {
value = issue.getPropertyIdName(pclass.getId(), IdName.NULL).toString();
}
ed.setText(value);
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeTextFieldForProperty");
return propNode;
}
private PropertyNode makeNumericFieldForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeNumericFieldForProperty(");
TextField ed = new TextField();
ed.addEventFilter(KeyEvent.KEY_TYPED, (keyEvent) -> {
if (!"0123456789.,-".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
});
PropertyNode propNode = new PropertyNode(issue, pclass, ed) {
@Override
public void updateData(boolean save) {
if (save) {
// Hint: only 3 fraction digits are saved by JIRA
try {
Number n = NumberFormat.getInstance().parse(ed.getText());
issue.setPropertyValue(pclass.getId(), n);
}
catch (ParseException e) {
}
}
else {
Number n = (Number)issue.getPropertyValue(pclass.getId(), null);
String value = "";
if (n != null) {
value = NumberFormat.getInstance().format(n);
}
ed.setText(value);
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeNumericFieldForProperty");
return propNode;
}
private PropertyNode makeTextAreaForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeTextAreaForProperty(");
PropertyNode ret = null;
try {
final IssueService srv = Globals.getIssueService();
final IssuePropertyEditor editor = srv.getPropertyEditor(issueTaskPane, issue, pclass.getId());
// Create a standard TextArea if no special property editor is provided.
final TextArea textArea = editor != null ? null : new TextArea();
if (textArea != null) {
// Forward to next control on TAB
textArea.addEventFilter(KeyEvent.KEY_PRESSED, (e) -> {
if (e.getCode().equals(KeyCode.TAB)) {
TextAreaSkin skin = (TextAreaSkin)textArea.getSkin();
if (e.isShiftDown()) {
skin.getBehavior().traversePrevious();
}
else {
skin.getBehavior().traverseNext();
}
e.consume();
}
});
textArea.setWrapText(true);
textArea.setMinHeight(60);
textArea.setMaxHeight(Double.MAX_VALUE);
textArea.setPrefHeight(60);
}
VBox vboxEditor = null;
if (editor != null) {
final Control editorControl = (Control)editor.getNode();
VBox.setVgrow(editorControl, Priority.ALWAYS);
vboxEditor = new VBox();
vboxEditor.getChildren().clear();
vboxEditor.getChildren().add(editorControl);
vboxEditor.setStyle("-fx-border-color: LIGHTGREY;-fx-border-width: 1px;");
vboxEditor.setMinHeight(100);
vboxEditor.setMaxHeight(Double.MAX_VALUE);
vboxEditor.setPrefHeight(100);
}
Region nodeRegion = textArea != null ? textArea : vboxEditor;
ret = new PropertyNode(issue, pclass, nodeRegion) {
@Override
public void updateData(boolean save) {
if (editor != null) {
editor.updateData(save);
}
else {
if (save) {
String text = textArea.getText();
issue.setPropertyString(pclass.getId(), text);
}
else {
String text = issue.getPropertyString(pclass.getId(), "");
textArea.setText(text);
}
}
}
};
} catch (Exception e) {
log.log(Level.WARNING, "Failed to create text area for property=" + pclass);
}
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeTextAreaForProperty");
return ret;
}
private PropertyNode makeChoiceBoxForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeChoiceBoxForProperty(");
ComboBox<IdName> ctrl = new ComboBox<IdName>();
PropertyNode propNode = new PropertyNode(issue, pclass, ctrl) {
@Override
public void updateData(boolean save) {
if (save) {
IdName idn = ctrl.getSelectionModel().getSelectedItem();
issue.setPropertyIdName(pclass.getId(), idn);
}
else {
List<IdName> selectList = pclass.getSelectList();
ctrl.setItems(FXCollections.observableArrayList(selectList));
Object value = issue.getPropertyValue(pclass.getId(), pclass.getDefaultValue());
for (IdName item : selectList) {
if (item.equals(value) || item.getId().equals(value)) {
ctrl.setValue(item);
break;
}
}
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeChoiceBoxForProperty");
return propNode;
}
@SuppressWarnings("rawtypes")
private PropertyNode makeChoiceBoxForPropertyArray(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeChoiceBoxForProperty(");
List<IdName> selectList = pclass.getSelectList();
CheckComboBox<IdName> ctrl = new CheckComboBox<IdName>(FXCollections.observableArrayList(selectList));
PropertyNode propNode = new PropertyNode(issue, pclass, ctrl) {
@Override
public void updateData(boolean save) {
if (save) {
List<IdName> idns = new ArrayList<IdName>(ctrl.getCheckModel().getCheckedItems());
Object value = null;
switch(pclass.getType()) {
case PropertyClass.TYPE_STRING:
value = idns.stream().map((idn) -> idn.getId()).collect(Collectors.toList());
break;
case PropertyClass.TYPE_ID_NAME:
value = idns;
break;
}
if (!pclass.isArray()) {
if (value != null) {
List list = (List)value;
if (!list.isEmpty()) {
value = list.get(0);
}
}
}
issue.setPropertyValue(pclass.getId(), value);
}
else {
Object propValue = issue.getPropertyValue(pclass.getId(), null);
if (propValue != null) {
for (IdName idn : selectList) {
boolean checked = false;
if (propValue instanceof List) {
switch(pclass.getType()) {
case PropertyClass.TYPE_STRING:
checked = ((List)propValue).contains(idn.getId());
break;
case PropertyClass.TYPE_ID_NAME:
checked = ((List)propValue).contains(idn);
break;
}
}
else if (propValue instanceof String) {
switch(pclass.getType()) {
case PropertyClass.TYPE_STRING:
checked = propValue.equals(idn.getId());
break;
case PropertyClass.TYPE_ID_NAME:
checked = propValue.equals(idn);
break;
}
}
if (checked) {
ctrl.getCheckModel().check(idn);
}
}
}
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeChoiceBoxForProperty");
return propNode;
}
private PropertyNode makeAutoCompletionNode(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeAutoCompletionNode(" + pclass.getId());
String recentCaption = resb.getString("autocomplete.recentCaption");
String suggestionsCaption = resb.getString("autocomplete.suggestionsCaption");
List<IdName> recentItems = pclass.getSelectList();
if (recentItems == null) recentItems = new ArrayList<IdName>();
ExtractImage<IdName> extractImage = (item) -> item.getImage();
AutoCompletionComboBox<IdName> comboBox = AutoCompletions.createAutoCompletionNode(extractImage, recentCaption,
suggestionsCaption, recentItems, pclass.getAutoCompletionSuggest());
PropertyNode propNode = new PropertyNode(issue, pclass, comboBox) {
@Override
public void updateData(boolean save) {
if (save) {
IdName idn = comboBox.getBinding().getSelectedItem();
issue.setPropertyValue(pclass.getId(), idn);
}
else {
IdName item = (IdName)issue.getPropertyValue(pclass.getId(), null);
if (item != null) {
comboBox.getBinding().select(item);
}
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeAutoCompletionNode");
return propNode;
}
private PropertyNode makeCheckBoxForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeCheckBoxForProperty(");
CheckBox ctrl = new CheckBox();
PropertyNode propNode = new PropertyNode(issue, pclass, ctrl) {
@Override
public void updateData(boolean save) {
if (save) {
boolean value = ctrl.isSelected();
issue.setPropertyBoolean(pclass.getId(), value);
}
else {
boolean value = issue.getPropertyBoolean(pclass.getId(), Boolean.FALSE);
ctrl.setSelected(value);
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeCheckBoxForProperty");
return propNode;
}
private PropertyNode makeDatePickerForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeDatePickerForProperty(");
DatePicker ctrl = new DatePicker();
PropertyNode propNode = new PropertyNode(issue, pclass, ctrl) {
@Override
public void updateData(boolean save) {
if (save) {
LocalDate ldate = ctrl.getValue();
String iso = null;
if (ldate != null) {
iso = ldate.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
issue.setPropertyString(pclass.getId(), iso);
}
else {
String iso = issue.getPropertyString(pclass.getId(), "");
if (iso != null && iso.length() != 0) {
if (iso.length() > 10) {
iso = iso.substring(0, 10);
}
LocalDate ldate = LocalDate.parse(iso);
ctrl.setValue(ldate);
}
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeDatePickerForProperty");
return propNode;
}
private PropertyNode makeDateTimePickerForProperty(Issue issue, PropertyClass pclass) {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeDateTimePickerForProperty(");
DateTimePicker ctrl = new DateTimePicker();
PropertyNode propNode = new PropertyNode(issue, pclass, ctrl) {
@Override
public void updateData(boolean save) {
if (save) {
LocalDateTime ldate = ctrl.getDateTimeValue();
String iso = ISODate.toISO(ldate);
issue.setPropertyString(pclass.getId(), iso);
}
else {
String iso = issue.getPropertyString(pclass.getId(), "");
if (iso != null && iso.length() != 0) {
try {
LocalDateTime ldate = ISODate.toLocalDateTime(iso);
ctrl.setDateTimeValue(ldate);
} catch (ParseException e) {
log.log(Level.WARNING, "Failed to parse ISO date provided by JIRA for property " + pclass.getId() + "=" + iso, e);
}
}
}
}
};
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeDateTimePickerForProperty");
return propNode;
}
private PropertyNode makeIssuePropertyEditor(Issue issue, PropertyClass pclass) throws Exception {
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "makeIssuePropertyEditor(");
PropertyNode propNode = null;
final IssuePropertyEditor propEdit = Globals.getIssueService().getPropertyEditor(issueTaskPane, issue, pclass.getId());
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "propEdit=" + propEdit);
if (propEdit != null) {
Node control = propEdit.getNode();
propNode = new PropertyNode(issue, pclass, control) {
public void updateData(boolean save) {
propEdit.updateData(save);
}
};
}
if (log.isLoggable(Level.FINE)) log.log(Level.FINE, ")makeIssuePropertyEditor");
return propNode;
}
public Node getFirstControl() {
return firstControl;
}
}